package org.apache.ibatis.executor;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cache.TransactionalCacheManager;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

import java.sql.SQLException;
import java.util.List;

/**
 * CachingExecutor是一个Executor接口的装饰器，
 * 它为Executor对象增加了二级缓存的相关功能
 *  CachingExecutor一个装饰器类，用于给目标 Executor 增加二级缓存功能。
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class CachingExecutor implements Executor {

    private final Executor delegate;
    private final TransactionalCacheManager tcm = new TransactionalCacheManager();

    public CachingExecutor(Executor delegate) {
        this.delegate = delegate;
        delegate.setExecutorWrapper(this);
    }

    @Override
    public Transaction getTransaction() {
        return delegate.getTransaction();
    }

    @Override
    public void close(boolean forceRollback) {
        try {
            //issues #499, #524 and #573
            if (forceRollback) {
                tcm.rollback();
            } else {
                tcm.commit();
            }
        } finally {
            delegate.close(forceRollback);
        }
    }

    @Override
    public boolean isClosed() {
        return delegate.isClosed();
    }

    /**
     * 默认情况下，insert、update 和 delete 操作都会清空一二级缓存。
     */
    @Override
    public int update(MappedStatement ms, Object parameterObject) throws SQLException {
        // 刷新二级缓存
        flushCacheIfRequired(ms);
        return delegate.update(ms, parameterObject);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获取BoundSql对象，创建查询语句对应的CacheKey对象
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // 创建 CacheKey
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        // 调用重载方法
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

    @Override
    public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
        flushCacheIfRequired(ms);
        return delegate.queryCursor(ms, parameter, rowBounds);
    }

    /**
     * 检测是否开启了二级缓存，如果没有开启二级缓存，则直接调用底层Executor对象的query()方法查询数据库。
     * 如果开启了二级缓存，则检测查询操作是否包含输出类型的参数，如果是这种情况，则报错。
     * 调用TransactionalCacheManager.getObject()方法查询二级缓存，如果二级缓存中查找到相应的结果对象，则直接将该结果对象返回。
     * 注意二级缓存是从 MappedStatement 中获取的，而非由 CachingExecutor 创建。
     * 由于 MappedStatement 存在于全局配置中，可以多个 CachingExecutor 获取到，这样就会出现线程安全问题。
     * 除此之外，若不加以控制，多个事务共用一个缓存实例，会导致脏读问题。
     * 线程安全问题可以通过 SynchronizedCache 装饰类解决，该装饰类会在 Cache 实例构造期间被添加上。
     * 至于脏读问题，需要借助其他类来处理，也就是上面代码中 tcm 变量对应的类型。
     *
     * @param ms
     * @param parameterObject
     * @param rowBounds
     * @param resultHandler
     * @param key
     * @param boundSql
     * @param <E>
     * @return
     * @throws SQLException
     */
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
            throws SQLException {
        // 1、获取查询语句所在命名空间对应的二级缓存
        // 从 MappedStatement 中获取 Cache，注意这里的 Cache 并非是在 CachingExecutor 中创建的
        Cache cache = ms.getCache();
        // 2、是否开启了二级缓存功能
        // 如果配置文件中没有配置 <cache>，则 cache 为空
        if (cache != null) {
            // 根据＜select＞节点的配置，决定是否清空二级缓存
            flushCacheIfRequired(ms);
            // 检测SQL节点的useCache配置以及是否使用了resultHandler配置
            if (ms.isUseCache() && resultHandler == null) {
                // 3、二级缓存不能保存输出类型的参数，如果查询操作调用了包含输出参数的存储过程，则报错
                ensureNoOutParams(ms, boundSql);

                // 4、查询二级缓存
                @SuppressWarnings("unchecked")
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    // 5、二级缓存没有相应的结果对象，调用封装的Executor对象的query()方法，
                    // 正如前面介绍的，其中会先查询一级缓存
                    // 若缓存未命中，则调用被装饰类的 query 方法
                    list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    // 将查询结果保存到TransactionalCache.entriesToAddOnCornrnit集合中
                    tcm.putObject(cache, key, list);
                }
                return list;
            }
        }
        // 调用被装饰类的 query 方法
        // 没有启动二级缓存，直接调用底层Executor执行数据库查询操作
        return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

    @Override
    public List<BatchResult> flushStatements() throws SQLException {
        return delegate.flushStatements();
    }

    @Override
    public void commit(boolean required) throws SQLException {
        delegate.commit(required);
        tcm.commit();
    }

    @Override
    public void rollback(boolean required) throws SQLException {
        try {
            delegate.rollback(required);
        } finally {
            if (required) {
                tcm.rollback();
            }
        }
    }

    private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
        if (ms.getStatementType() == StatementType.CALLABLE) {
            for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
                if (parameterMapping.getMode() != ParameterMode.IN) {
                    throw new ExecutorException("Caching stored procedures with OUT params is not supported.  Please configure useCache=false in " + ms.getId() + " statement.");
                }
            }
        }
    }

    @Override
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
    }

    @Override
    public boolean isCached(MappedStatement ms, CacheKey key) {
        return delegate.isCached(ms, key);
    }

    @Override
    public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
        delegate.deferLoad(ms, resultObject, property, key, targetType);
    }

    @Override
    public void clearLocalCache() {
        delegate.clearLocalCache();
    }

    private void flushCacheIfRequired(MappedStatement ms) {
        Cache cache = ms.getCache();
        if (cache != null && ms.isFlushCacheRequired()) {
            tcm.clear(cache);
        }
    }

    @Override
    public void setExecutorWrapper(Executor executor) {
        throw new UnsupportedOperationException("This method should not be called");
    }

}
